const util = {}

const MAX_ARRAY_INDEX = Math.pow(2, 53) - 1
const nativeIsArray = Array.isArray

const ObjProto = Object.prototype

const shallowProperty = function (key) {
  return function (obj) {
    return obj == null ? void 0 : obj[key]
  }
}

const has = function (obj, path) {
  return obj != null && hasOwnProperty.call(obj, path)
}

const hasEnumBug = !{ toString: null }.propertyIsEnumerable('toString')
const nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
  'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']
var collectNonEnumProps = function (obj, keys) {
  var nonEnumIdx = nonEnumerableProps.length
  var constructor = obj.constructor
  var proto = util.isFunction(constructor) && constructor.prototype || ObjProto

  var prop = 'constructor'
  if (has(obj, prop) && !util.contains(keys, prop)) keys.push(prop)

  while (nonEnumIdx--) {
    prop = nonEnumerableProps[nonEnumIdx]
    if (prop in obj && obj[prop] !== proto[prop] && !util.contains(keys, prop)) {
      keys.push(prop)
    }
  }
}


const getLength = shallowProperty('length')
const isArrayLike = function (collection) {
  const length = getLength(collection)
  return typeof length === 'number' && length >= 0 && length <= MAX_ARRAY_INDEX
}

const optimizeCb = function (func, context, argCount) {
  if (context === void 0) return func
  switch (argCount == null ? 3 : argCount) {
    case 1: return function (value) {
      return func.call(context, value)
    }
    case 2: return function (value, other) {
      return func.call(context, value, other)
    }
    case 3: return function (value, index, collection) {
      return func.call(context, value, index, collection)
    }
    case 4: return function (accumulator, value, index, collection) {
      return func.call(context, accumulator, value, index, collection)
    }
  }
  return function () {
    return func.apply(context, arguments)
  }
}

const cb = function (value, context, argCount) {
  if (value == null) return util.identity
  if (util.isFunction(value)) return optimizeCb(value, context, argCount)
  if (util.isObject(value)) return util.matcher(value)
  return util.property(value)
}

const property = function (key) {
  return function (obj) {
    return obj == null ? void 0 : obj[key]
  }
}

const createAssigner = function (keysFunc, undefinedOnly) {
  return function (obj) {
    var length = arguments.length
    if (length < 2 || obj == null) return obj
    for (var index = 1; index < length; index++) {
      var source = arguments[index]
      var keys = keysFunc(source)
      var l = keys.length
      for (var i = 0; i < l; i++) {
        var key = keys[i]
        if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key]
      }
    }
    return obj
  }
}

util.property = property

util.isArray = nativeIsArray || function (obj) {
  return toString.call(obj) === '[object Array]'
}

util.isString = function (obj) {
  return toString.call(obj) === '[object String]'
}

util.isArguments = function (obj) {
  return toString.call(obj) === '[object Arguments]'
}

util.isNumber = function (obj) {
  return toString.call(obj) === '[object Number]'
}

util.isFunction = function (obj) {
  return toString.call(obj) === '[object Function]'
}

util.isObject = function (obj) {
  const type = typeof obj
  return type === 'function' || type === 'object' && !!obj
}

util.has = function (obj, path) {
  return obj != null && hasOwnProperty.call(obj, path)
}

util.keys = Object.keys

util.isEmpty = function (obj) {
  if (obj == null) return true
  if (this.isNumber(obj)) obj = String(obj)
  if (isArrayLike(obj) && (this.isArray(obj) || this.isString(obj) || this.isArguments(obj))) {
    return obj.length === 0
  }
  return this.keys(obj).length === 0
}

util.each = util.forEach = function (obj, iteratee, context) {
  iteratee = optimizeCb(iteratee, context)
  var i, length
  if (isArrayLike(obj)) {
    for (i = 0, length = obj.length; i < length; i++) {
      iteratee(obj[i], i, obj)
    }
  } else {
    var keys = util.keys(obj)
    for (i = 0, length = keys.length; i < length; i++) {
      iteratee(obj[keys[i]], keys[i], obj)
    }
  }
  return obj
}

util.filter = util.select = function (obj, predicate, context) {
  var results = []
  predicate = cb(predicate, context)
  util.each(obj, function (value, index, list) {
    if (predicate(value, index, list)) results.push(value)
  })
  return results
}

// Return the results of applying the iteratee to each element.
util.map = function (obj, iteratee, context) {
  iteratee = cb(iteratee, context)
  var keys = !isArrayLike(obj) && util.keys(obj)
  var length = (keys || obj).length
  var results = Array(length)
  for (var index = 0; index < length; index++) {
    var currentKey = keys ? keys[index] : index
    results[index] = iteratee(obj[currentKey], currentKey, obj)
  }
  return results
}

util.matcher = util.matches = function (attrs) {
  attrs = util.extendOwn({}, attrs)
  return function (obj) {
    return util.isMatch(obj, attrs)
  }
}

util.isMatch = function (object, attrs) {
  var keys = util.keys(attrs); var length = keys.length
  if (object == null) return !length
  var obj = Object(object)
  for (var i = 0; i < length; i++) {
    var key = keys[i]
    if (attrs[key] !== obj[key] || !(key in obj)) return false
  }
  return true
}

util.extendOwn = util.assign = createAssigner(util.keys)

util.allKeys = function (obj) {
  if (!util.isObject(obj)) return []
  var keys = []
  for (var key in obj) keys.push(key)

  if (hasEnumBug) collectNonEnumProps(obj, keys)
  return keys
}

util.extend = createAssigner(util.allKeys)


util.isPhone = function (rule, value, callback) {
  if (value === '1234567') {
    callback();
    return;
  }
  if (!(/^1[34578]\d{9}$/.test(value))) {
    callback(new Error(rule.message))
    return
  }

  callback()
}
util.objExtend = function (to, _from) {
  if (this.isObject(to) && this.isObject(_from)) {
    for (var obj in _from) {
      to[obj] = _from[obj]
    }
    return to
  }
  return false
}

export default {
  name: '_',
  default: util
}
